﻿using UnityEngine;
using UnityEditor;
using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

namespace Hont.AStar
{
    [CustomEditor(typeof(HontAStarUnity))]
    public class HontAStarUnity_Inspector : Editor
    {
        public struct SubdivsCache
        {
            public Bounds Bounds;
            public bool IsWalkable;
            public int Cost;
            public int Mask;
        }

        HontAStarUnity mAStar;
        bool mBuildSetting;
        bool mInvertCollision;
        bool mIsEnableSizeFreeHandle;
        bool mIsFreeHandleInit;
        bool mXSubdivsTog = true;
        bool mYSubdivsTog;
        bool mZSubdivsTog = true;
        Vector3 mMaxLengthFreeHandle;
        Vector3 mMaxWidthFreeHandle;
        Vector3 mMaxHeightFreeHandle;


        void Awake()
        {
            mAStar = base.target as HontAStarUnity;
        }

        void OnSceneGUI()
        {
            if (!mIsEnableSizeFreeHandle) return;

            if (mIsFreeHandleInit == false)
            {
                mIsFreeHandleInit = true;
            }

            var minLength = default(Vector3);
            var minHeight = default(Vector3);
            var minWidth = default(Vector3);

            minLength = mAStar.Bounds.center;
            minLength.x -= mAStar.Bounds.extents.x;
            mMaxLengthFreeHandle = mAStar.Bounds.center;
            mMaxLengthFreeHandle.x += mAStar.Bounds.extents.x;

            minWidth = mAStar.Bounds.center;
            minWidth.z -= mAStar.Bounds.extents.z;
            mMaxWidthFreeHandle = mAStar.Bounds.center;
            mMaxWidthFreeHandle.z += mAStar.Bounds.extents.z;

            minHeight = mAStar.Bounds.center;
            minHeight.y -= mAStar.Bounds.extents.y;
            mMaxHeightFreeHandle = mAStar.Bounds.center;
            mMaxHeightFreeHandle.y += mAStar.Bounds.extents.y;

            mMaxLengthFreeHandle = Handles.FreeMoveHandle(mMaxLengthFreeHandle, Quaternion.identity, HandleUtility.GetHandleSize(mMaxLengthFreeHandle) * 0.05f, Vector3.zero, Handles.DotHandleCap);
            mMaxWidthFreeHandle = Handles.FreeMoveHandle(mMaxWidthFreeHandle, Quaternion.identity, HandleUtility.GetHandleSize(mMaxLengthFreeHandle) * 0.05f, Vector3.zero, Handles.DotHandleCap);
            mMaxHeightFreeHandle = Handles.FreeMoveHandle(mMaxHeightFreeHandle, Quaternion.identity, HandleUtility.GetHandleSize(mMaxLengthFreeHandle) * 0.05f, Vector3.zero, Handles.DotHandleCap);

            minLength.y = mAStar.Bounds.center.y;
            minLength.z = mAStar.Bounds.center.z;
            mMaxLengthFreeHandle.y = mAStar.Bounds.center.y;
            mMaxLengthFreeHandle.z = mAStar.Bounds.center.z;
            mAStar.lenght = (int)(Mathf.Abs(mMaxLengthFreeHandle.x - minLength.x) / mAStar.mappingLengthSize);

            minHeight.x = mAStar.Bounds.center.x;
            minHeight.z = mAStar.Bounds.center.z;
            mMaxHeightFreeHandle.x = mAStar.Bounds.center.x;
            mMaxHeightFreeHandle.z = mAStar.Bounds.center.z;
            mAStar.height = (int)(Mathf.Abs(mMaxHeightFreeHandle.y - minHeight.y) / mAStar.mappingHeightSize);

            minWidth.x = mAStar.Bounds.center.x;
            minWidth.y = mAStar.Bounds.center.y;
            mMaxWidthFreeHandle.x = mAStar.Bounds.center.x;
            mMaxWidthFreeHandle.y = mAStar.Bounds.center.y;
            mAStar.width = (int)(Mathf.Abs(mMaxWidthFreeHandle.z - minWidth.z) / mAStar.mappingWidthSize);
        }

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            RefreshAStarEditorBoxData();

            GUILayout.BeginVertical(GUI.skin.box);
            GUILayout.Label(i18N.TOTAL_SIZE + ": " + mAStar.TotalSize);
            GUILayout.EndVertical();

            mBuildSetting = EditorGUILayout.Foldout(mBuildSetting, i18N.BUILD_SETTINGS);
            if (mBuildSetting)
            {
                GUILayout.BeginVertical(GUI.skin.box);
                mIsEnableSizeFreeHandle = GUILayout.Toggle(mIsEnableSizeFreeHandle, i18N.ENABLE_FREE_HANDLE, GUI.skin.button);
                if (!mIsEnableSizeFreeHandle && mIsFreeHandleInit)
                {
                    SceneView.RepaintAll();
                    mIsFreeHandleInit = false;
                }
                else if (mIsEnableSizeFreeHandle && !mIsFreeHandleInit)
                {
                    SceneView.RepaintAll();
                }

                if (GUILayout.Button(i18N.INVERT))
                {
                    ForeachNodes(m => m.isWalkable = !m.isWalkable);
                    SceneView.RepaintAll();
                }

                if (GUILayout.Button(i18N.NODES_TO_OBSTACLE))
                {
                    ForeachNodes(m => m.isWalkable = false);
                    SceneView.RepaintAll();
                }

                GUILayout.BeginVertical(GUI.skin.box);

                GUILayout.BeginHorizontal();
                if (GUILayout.Button(i18N.MATCH_MESH))
                {
                    MatchMesh();
                }

                if (GUILayout.Button(i18N.MATCH_COLLIDER))
                {
                    MatchCollider();
                }
                GUILayout.EndHorizontal();

                mInvertCollision = EditorGUILayout.Toggle(i18N.INVERT_COLLISION, mInvertCollision);
                GUILayout.EndVertical();

                GUILayout.BeginVertical(GUI.skin.box);
                if (GUILayout.Button(i18N.SUBDIVS))
                {
                    SubdivsGrid(mXSubdivsTog, mYSubdivsTog, mZSubdivsTog);
                }

                GUILayout.BeginHorizontal();
                mXSubdivsTog = GUILayout.Toggle(mXSubdivsTog, "x", GUILayout.MaxWidth(90));
                mYSubdivsTog = GUILayout.Toggle(mYSubdivsTog, "y", GUILayout.MaxWidth(94));
                mZSubdivsTog = GUILayout.Toggle(mZSubdivsTog, "z", GUILayout.MaxWidth(90));
                GUILayout.EndHorizontal();

                GUILayout.EndVertical();

                if (GUILayout.Button(i18N.MATCH_GRID_BOUNDS))
                {
                    MatchGridBounds();
                }
                if (GUILayout.Button(i18N.MATCH_FLOOR))
                {
                    MatchFloor();
                }
                GUILayout.EndVertical();
            }

            GUILayout.Space(10);

            if (GUILayout.Button(i18N.DEBUG_PATHFINDING))
            {
                if (mAStar.astarData == null)
                {
                    EditorUtility.DisplayDialog("Tip", "Not found 'AstarData'!", "ok");
                    return;
                }

                DebugPathfinding();
            }

            if (GUILayout.Button(i18N.CREATE_ASTAR_DATA))
            {
                CreateAStarData();
            }

            if (GUILayout.Button(i18N.SAVE_ASTAR_DATA))
            {
                PersistAstarData();
            }

            if (GUILayout.Button(i18N.LOAD_ASTAR_DATA))
            {
                LoadAStarData();
            }

            if (GUILayout.Button(i18N.CLEAR_HIER_DATA))
            {
                HontAStarUnityEditorHelper.ClearHierData(mAStar.transform);
            }
        }

        void CreateAStarData()
        {
            ResetAStarGizmosSettings();
            ResetGridScale();

            HontAStarUnityEditorHelper.ClearHierData(mAStar.transform);

            for (int lenghtIndex = 0; lenghtIndex < mAStar.lenght; lenghtIndex++)
            {
                for (int widthIndex = 0; widthIndex < mAStar.width; widthIndex++)
                {
                    for (int heightIndex = 0; heightIndex < mAStar.height; heightIndex++)
                    {
                        var x = lenghtIndex * mAStar.mappingLengthSize;
                        var y = heightIndex * mAStar.mappingHeightSize;
                        var z = widthIndex * mAStar.mappingWidthSize;

                        var go = new GameObject(string.Format("Cube_x_{0}_y_{1}_z_{2}", lenghtIndex, heightIndex, widthIndex));
                        go.transform.parent = mAStar.transform;
                        go.transform.localPosition = new Vector3(x, y, z) + mAStar.MappingSize * 0.5f;
                        go.transform.localRotation = Quaternion.identity;
                        go.transform.localScale = Vector3.one;
                        var node = go.AddComponent<HontAStarUnityDebugNode>();
                        node.Init(mAStar, new Position(lenghtIndex, heightIndex, widthIndex));

                        node.customMappingTransform = new GameObject("CustomMappingPoint").transform;
                        node.customMappingTransform.parent = go.transform;
                        node.customMappingTransform.localPosition = Vector3.zero;
                        node.customMappingTransform.localScale = Vector3.one;
                    }
                }
            }

            mAStar.advanceSetting.drawGizmos = true;
        }

        void RefreshAStarEditorBoxData()
        {
            if (mAStar == null || mAStar.Grid == null) return;

            foreach (Position item in mAStar.Grid)
            {
                var node = mAStar.Grid.GetUserData(item) as HontAStarUnityDebugNode;

                if (node == null) continue;

                node.transform.localPosition = new Vector3(item.X * mAStar.mappingLengthSize, item.Y * mAStar.mappingHeightSize, item.Z * mAStar.mappingWidthSize);
                node.transform.localPosition += mAStar.MappingSize * 0.5f;
            }
        }

        void LoadAStarData()
        {
            if (mAStar.astarData == null) return;

            ResetGridScale();
            ResetAStarGizmosSettings();

            var grid = mAStar.LoadAStarData(mAStar.astarData, node =>
            {
                var go = new GameObject(string.Format("Cube_x_{0}_y_{1}_z_{2}", node.X, node.Y, node.Z));
                go.transform.parent = mAStar.transform;
                go.transform.localPosition = node.MappingPos;
                go.transform.localRotation = Quaternion.identity;
                go.transform.localScale = Vector3.one;

                var userData = go.AddComponent<HontAStarUnityDebugNode>();
                userData.Init(mAStar, new Position(node.X, node.Y, node.Z));
                userData.cost = node.Cost;
                userData.mask = node.Mask;
                userData.isWalkable = node.IsWalkable;

                userData.customMappingTransform = new GameObject("CustomMappingPoint").transform;
                userData.customMappingTransform.parent = go.transform;
                userData.customMappingTransform.localPosition = node.CustomMappingLocalPos;

                return userData;
            });

            mAStar.InitAStarData(grid);
            mAStar.BuildOctTree((c) => (c as HontAStarUnityDebugNode).transform.localPosition);

            mAStar.advanceSetting.drawGizmos = true;
        }

        void PersistAstarData()
        {
            var nodeArr = HontAStarUnityEditorHelper.GetChildDebugNodes(mAStar);

            if (nodeArr.Length != mAStar.TotalSize)
            {
                EditorUtility.DisplayDialog("Error!", "Total Size Error! Persist Failure!", "ok");
                return;
            }

            var astarData = ScriptableObject.CreateInstance<HontAStarUnityPersistData>();
            astarData.lenght = mAStar.lenght;
            astarData.width = mAStar.width;
            astarData.height = mAStar.height;
            astarData.mappingLengthSize = mAStar.mappingLengthSize;
            astarData.mappingWidthSize = mAStar.mappingWidthSize;
            astarData.mappingHeightSize = mAStar.mappingHeightSize;
            astarData.nodeArr = new HontAStarUnityPersistData.Node[nodeArr.Length];

            for (int i = 0; i < astarData.nodeArr.Length; i++)
            {
                astarData.nodeArr[i] = new HontAStarUnityPersistData.Node();
                var item = astarData.nodeArr[i];

                item.Cost = nodeArr[i].cost;
                item.Mask = nodeArr[i].mask;
                item.IsWalkable = nodeArr[i].isWalkable;

                item.MappingPos = nodeArr[i].transform.localPosition;
                if (nodeArr[i].customMappingTransform != null)
                    item.CustomMappingLocalPos = nodeArr[i].customMappingTransform.localPosition;

                item.X = nodeArr[i].astar_x;
                item.Y = nodeArr[i].astar_y;
                item.Z = nodeArr[i].astar_z;
            }

            var path = "";
            var recoveryLinkFlag = false;

            if (mAStar.astarData != null)
            {
                recoveryLinkFlag = true;
                path = AssetDatabase.GetAssetPath(mAStar.astarData);

                if (!string.IsNullOrEmpty(path))
                    AssetDatabase.DeleteAsset(path);
            }
            else
            {
                path = EditorUtility.SaveFilePanelInProject("Save Config", "AStar", "asset", "");
            }

            if (!string.IsNullOrEmpty(path))
                AssetDatabase.CreateAsset(astarData, path);

            if (recoveryLinkFlag)
            {
                mAStar.astarData = AssetDatabase.LoadAssetAtPath<HontAStarUnityPersistData>(path);
            }
        }

        void ForeachNodes(Action<HontAStarUnityDebugNode> action)
        {
            var nodeArr = HontAStarUnityEditorHelper.GetChildDebugNodes(mAStar);

            foreach (var item in nodeArr)
                action(item);
        }

        void MatchGridBounds()
        {
            var root = mAStar.transform.root;

            var hits = Physics.SphereCastAll(new Vector3(0, 0, 0), 10000, Vector3.one);

            var boundsArr = hits
                .Where(m => m.collider != null)
                .Where(m => m.collider.transform.root == root)
                .Select(m => m.collider.bounds)
                .ToArray();

            var hugeBounds = HontAStarUnityEditorHelper.CombineBounds(boundsArr);

            mAStar.lenght = Mathf.Max(1, (int)(hugeBounds.size.x / mAStar.mappingLengthSize));
            mAStar.height = Mathf.Max(1, (int)(hugeBounds.size.y / mAStar.mappingHeightSize));
            mAStar.width = Mathf.Max(1, (int)(hugeBounds.size.z / mAStar.mappingWidthSize));

            mAStar.transform.position = hugeBounds.min + mAStar.MappingSize * 0.5f;

            SceneView.RepaintAll();
        }

        void MatchMesh()
        {
            var bounds = mAStar.Bounds;
            var obstacles = Physics.BoxCastAll(bounds.center, bounds.size, Vector3.forward, Quaternion.identity, 0);
            var nodeArr = HontAStarUnityEditorHelper.GetChildDebugNodes(mAStar);

            var obstacleList = new List<HontAStarUnityDebugNode>();
            var closedOtherColliderList = new HashSet<Collider>();
            var attachedMeshColliderList = new HashSet<MeshFilter>();

            foreach (var item in obstacles)
            {
                foreach (var collider in item.transform.GetComponentsInChildren<Collider>())
                {
                    if (collider is MeshCollider) continue;
                    closedOtherColliderList.Add(collider);
                }
            }

            foreach (var item in obstacles)
            {
                foreach (var meshFilter in item.transform.GetComponentsInChildren<MeshFilter>())
                {
                    if (meshFilter.GetComponent<MeshCollider>() != null) continue;
                    if (meshFilter.sharedMesh == null) continue;

                    attachedMeshColliderList.Add(meshFilter);
                }
            }

            foreach (var item in closedOtherColliderList)
            {
                item.enabled = false;
            }

            foreach (var item in attachedMeshColliderList)
            {
                var meshCollider = item.gameObject.AddComponent<MeshCollider>();
                meshCollider.sharedMesh = item.sharedMesh;
            }

            obstacles = Physics.BoxCastAll(bounds.center, bounds.size, Vector3.forward, Quaternion.identity, 0);

            foreach (var item in obstacles)
            {
                if (item.transform == null) continue;
                if (item.collider == null) continue;
                var meshFilter = item.transform.GetComponent<MeshFilter>();
                if (meshFilter == null) continue;
                if (!mAStar.Bounds.Intersects(item.collider.bounds)) continue;

                for (int i = 0; i < nodeArr.Length; i++)
                {
                    var astarBox = nodeArr[i];

                    if (Physics.Raycast(new Ray(astarBox.Bounds.center, Vector3.up), 0))
                    {
                        obstacleList.Add(astarBox);
                    }
                }
            }

            if (mInvertCollision)
                obstacleList.ForEach(m => m.isWalkable = true);
            else
                obstacleList.ForEach(m => m.isWalkable = false);

            foreach (var item in attachedMeshColliderList)
            {
                var meshCollider = item.gameObject.GetComponent<MeshCollider>();

                DestroyImmediate(meshCollider);
            }

            foreach (var item in closedOtherColliderList)
            {
                item.enabled = true;
            }
        }

        void MatchCollider()
        {
            var bounds = mAStar.Bounds;
            var obstacles = Physics.BoxCastAll(bounds.center, bounds.size, Vector3.forward, Quaternion.identity, 0);
            var nodeArr = HontAStarUnityEditorHelper.GetChildDebugNodes(mAStar);

            var obstacleList = new List<HontAStarUnityDebugNode>();

            foreach (var item in obstacles)
            {
                if (item.transform == null) continue;
                if (item.collider == null) continue;
                if (!mAStar.Bounds.Intersects(item.collider.bounds)) continue;

                for (int i = 0; i < nodeArr.Length; i++)
                {
                    var astarBox = nodeArr[i];

                    if (item.collider.bounds.Intersects(astarBox.Bounds))
                        obstacleList.Add(astarBox);
                }
            }

            if (mInvertCollision)
                obstacleList.ForEach(m => m.isWalkable = true);
            else
                obstacleList.ForEach(m => m.isWalkable = false);
        }

        void MatchFloor()
        {
            var nodeArr = HontAStarUnityEditorHelper.GetChildDebugNodes(mAStar);

            for (int i = 0; i < nodeArr.Length; i++)
            {
                var astarBox = nodeArr[i];

                var hitInfo = new RaycastHit();
                Physics.Raycast(new Ray(astarBox.transform.position + Vector3.up * mAStar.height, Vector3.down), out hitInfo);

                if (hitInfo.transform != null)
                {
                    astarBox.customMappingTransform.position = hitInfo.point;
                }
            }
        }

        void SubdivsGrid(bool xSubdivs, bool ySubdivs, bool zSubdivs)
        {
            var cacheList = new List<SubdivsCache>();
            var nodeArr = HontAStarUnityEditorHelper.GetChildDebugNodes(mAStar);
            foreach (var item in nodeArr)
            {
                var cache = default(SubdivsCache);
                cache.Bounds = item.Bounds;
                cache.IsWalkable = item.isWalkable;
                cache.Cost = item.cost;
                cache.Mask = item.mask;

                cacheList.Add(cache);
            }

            if (xSubdivs)
            {
                mAStar.lenght *= 2;
                mAStar.mappingLengthSize = mAStar.mappingLengthSize * 0.5f;
            }
            if (zSubdivs)
            {
                mAStar.width *= 2;
                mAStar.mappingWidthSize = mAStar.mappingWidthSize * 0.5f;
            }
            if (ySubdivs)
            {
                mAStar.height *= 2;
                mAStar.mappingHeightSize = mAStar.mappingHeightSize * 0.5f;
            }

            HontAStarUnityEditorHelper.ClearHierData(mAStar.transform);
            CreateAStarData();

            nodeArr = HontAStarUnityEditorHelper.GetChildDebugNodes(mAStar);

            foreach (var node in nodeArr)
            {
                var targetCache = cacheList.FirstOrDefault(m => m.Bounds.Contains(node.Bounds.center));
                node.isWalkable = targetCache.IsWalkable;
                node.cost = targetCache.Cost;
                node.mask = targetCache.Mask;
            }
        }

        void DebugPathfinding()
        {
            var nodes = HontAStarUnityEditorHelper.GetChildDebugNodes(mAStar);
            var beginNode = nodes.FirstOrDefault(m => m.debugSetting.isBeginNode);
            var endNode = nodes.FirstOrDefault(m => m.debugSetting.isEndNode);

            if (beginNode != null && endNode != null)
            {
                #region ---Box---
                var stepPathArr = mAStar.StartPathfinding(beginNode.AStarPosition, endNode.AStarPosition);

                if (stepPathArr != null)
                {
                    for (int i = 0; i < stepPathArr.Length; i++)
                    {
                        var item = stepPathArr[i];

                        var node = mAStar.Grid.GetUserData(item) as HontAStarUnityDebugNode;
                        node.debugSetting.isInPath = true;
                    }

                    beginNode.debugSetting.isBeginNode = true;
                    endNode.debugSetting.isEndNode = true;
                }
                #endregion
                #region ---Path Point---
                var vectorPathArr = mAStar.StartPathfinding(beginNode.transform.position
                    , endNode.transform.position
                    , userData => (userData as HontAStarUnityDebugNode).transform.localPosition
                    , userData => (userData as HontAStarUnityDebugNode).OutputPosition
                    , -1
                    , true);

                if (vectorPathArr != null)
                {
                    var pathfindingGO = new GameObject("Preview Vector Path");
                    pathfindingGO.transform.parent = mAStar.transform;

                    var pathPoint = pathfindingGO.AddComponent<HontAStarUnityPathPointDebuger>();

                    pathPoint.pathArr = vectorPathArr;

                    beginNode.debugSetting.isBeginNode = true;
                    endNode.debugSetting.isEndNode = true;
                }
                #endregion
            }
        }

        #region ---Tools---

        void ResetGridScale()
        {
            var oldParent = mAStar.transform.parent;
            mAStar.transform.parent = null;
            mAStar.transform.localScale = Vector3.one;
            mAStar.transform.parent = oldParent;
        }

        void ResetAStarGizmosSettings()
        {
            mAStar.gizmosSetting.soloDisplayType = HontAStarUnity.SoloTypeEnum.All;
            mAStar.gizmosSetting.enableSizeSolo = false;
            mAStar.gizmosSetting.alpha = 0.4f;
            mAStar.gizmosSetting.soloHeightRange = Vector2.one;
            mAStar.gizmosSetting.soloLengthRange = Vector2.one;
            mAStar.gizmosSetting.soloWidthRange = Vector2.one;
        }

        #endregion
    }
}
